package com.waming.spring.sensitive.plugin.handler;

import com.waming.spring.sensitive.plugin.annotation.EncryptField;
import com.waming.spring.sensitive.plugin.annotation.SensitiveBinded;
import com.waming.spring.sensitive.plugin.mybatis.enums.PolicyType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 插件工具类，获取到对象的真实对象，可能存在多层代理
 * @author ;
 */
public class AnnotationHandler {
    /**类所有属性*/
    protected static final Map<Class<?>, Set<Field>> FIELDS_MAP = new ConcurrentHashMap<>();
    /**类中需要脱敏的属性*/
    private static final Map<Class<?>, Set<Field>> FIELDS_DESENSITIZE = new ConcurrentHashMap<>();
    /**类中需要加解密的属性*/
    private static final Map<Class<?>, Set<Field>> FIELDS_ENCRYPT = new ConcurrentHashMap<>();
    /**别名脱敏的属性*/
    private static final Map<Class<?>,Set<Field>> FIELDS_ALIASBIND = new ConcurrentHashMap<>();
    public AnnotationHandler() {
        super();
    }

    public <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }

    /**
     * 当类属性是否已经缓存过
     * @param cl 指定类
     * @return 是否缓存过
     */
    public static boolean isCache(Class<?> cl) {
        return FIELDS_MAP.containsKey(cl);
    }

    public Set<Field> getCahceFields(Class<?> cl, PolicyType safety) {
        Map<Class<?>, Set<Field>> fieldMap = getFieldMap(safety);
        return fieldMap.getOrDefault(cl,new HashSet<>());
    }

    public void addCahceField(Class<?> cl, Field field, PolicyType safety) {
        if (cl == null) {
            return;
        }
        Set<Field> fieldSet = getCahceFields(cl, safety);
        fieldSet.add(field);
        getFieldMap(safety).put(cl,fieldSet);
    }

    public void addCahceField(Class<?> cl, Set<Field> fields, PolicyType safety) {
        if (cl == null) {
            return;
        }
        Set<Field> fieldSet = getCahceFields(cl, safety);
        fieldSet.addAll(fields);
        getFieldMap(safety).put(cl,fieldSet);
    }

    /**
     * 处理类属性扫描并将其缓存，最终返回类属性
     * @param clazz 指定类
     * @return 值类型所有属性(限定非静态、final、volatile)
     */
    public Set<Field> parseClass(Class<?> clazz) {
        if (isCache(clazz)) {
            return getCahceFields(clazz, PolicyType.FIELD);
        }
        if (isFilter(clazz)) {
            return null;
        }
        Set<Field> fields = new HashSet<>();
        for (Field field : clazz.getDeclaredFields()) {
            boolean filter = Modifier.isStatic(field.getModifiers()) ||
                    Modifier.isFinal(field.getModifiers()) ||
                    Modifier.isVolatile(field.getModifiers());
            if (filter) {
                continue;
            }
            fields.add(field);
            processAnnotations(clazz, field);
        }
        if (!fields.isEmpty()) {
            addCahceField(clazz, fields, PolicyType.FIELD);
        }
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null && !superclass.equals(Object.class) && superclass.getDeclaredFields().length > 0) {
            parseClass(superclass);
        }
        return fields;
    }

    private static boolean isFilter(Class<?> clazz) {
        return Number.class.isAssignableFrom(clazz) ||
                Long.class.isAssignableFrom(clazz) ||
                TemporalAccessor.class.isAssignableFrom(clazz) ||
                Date.class.isAssignableFrom(clazz);
    }

    private void processAnnotations(Class<?> clazz, Field field) {
        //加密
        EncryptField encryptField = field.getAnnotation(EncryptField.class);
        //脱敏
        //SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
        //脱敏别名绑定
        SensitiveBinded sensitiveBinded = field.getAnnotation(SensitiveBinded.class);
        if (encryptField != null) {
            addCahceField(clazz, field, PolicyType.ENCRYPT);
        }
//        if (sensitiveField != null) {
//            addCahceField(clazz, field, PolicyType.SENSITIVE);
//        }
        if (sensitiveBinded != null) {
            addCahceField(clazz, field, PolicyType.ALIASBIND);
        }
    }

    private  Map<Class<?>, Set<Field>> getFieldMap(PolicyType safety) {
        switch (safety) {
            case ENCRYPT:
                return FIELDS_ENCRYPT;
            case SENSITIVE:
                return FIELDS_DESENSITIZE;
            case ALIASBIND:
                return FIELDS_ALIASBIND;
            default:
                return FIELDS_MAP;
        }
    }
}
